#!/usr/bin/env bash
## A calculator in pure-bash.
## I even made a pure-bash lexer and parser for this.
## Not gonna lie I feel pretty accomplished right now.
## ---------------------------------------------------
## Created by: Phate6660 [https://github.com/Phate6660]
## Dependencies: Only bash ;)

## Argument Gathering and Preparation
# Get the first arg (since that's all we need), and add a space between each character.
args="$1"
for ((i=0; i<${#args}; i++)); do
    args_spaces+="${args:${i}:1} "
done

# Read each space-separated character into the array.
# shellcheck disable=SC2162
read -a args_array <<< "${args_spaces}"

## Lexing
# Iterate over the argument array.
n=0
for char in "${args_array[@]}"; do
    # Check if "$char" is a parenthesis, and if so append to final_array.
    if [ "${char}" == "(" ] || [ "${char}" == ")" ]; then
        final_array+=("${char}")
        continue
    # Check if "$char" is a number, and if so append to final_array.
    elif [[ "${char}" =~ ^[0-9]+$ ]]; then
        final_array+=("${char}")
        continue
    # Check if "$char" is a math operation, and if so append to final_array.
    elif [ "${char}" == "^" ] \
        || [ "${char}" == "*" ] \
        || [ "${char}" == "/" ] \
        || [ "${char}" == "%" ] \
        || [ "${char}" == "+" ] \
        || [ "${char}" == "-" ]; then
        # Up n by 1 before and after appending the math operation to account for array index offsets.
        n=$((n + 1))
        final_array+=("${char}")
        n=$((n + 1))
        continue
    else
        # If the character isn't a digit, math operation, or parenthesis, display an error message and exit.
        echo -e "Only digits, parenthesis, '^', '*', '%', '/', '+', and '-', are supported.\n'${char}' is currently unsupported." && exit 1
    fi
done

if [ "$2" == "--debug" ]; then
    echo "final_array = ${final_array[*]}"
fi

## Parsing
final_array_count="${#final_array[@]}"
a=0
for ((b=0;b<=final_array_count;b++)); do
    if [ "${b}" = "${final_array_count}" ]; then
        if [ -n "${mn}" ]; then
            if [ "$2" == "--debug" ]; then
                echo "mn = ${mn[*]}"
            fi
            numbers="${mn[*]}"
            unset mn
            numbers="${numbers// /}"
            if [ "$2" == "--debug" ]; then
                echo "numbers = ${numbers}"
            fi
            number_array+=("${numbers}")
            unset numbers
            break
        else
            # No more looping is needed. Break!
            break
        fi
    else
        # Check if the character is an opening parenthesis...
        if [ "${final_array[${b}]}" == "(" ]; then
            unset op_found
            paren=true
            continue
        # Check if the character is a closing parenthesis...
        elif [ "${final_array[${b}]}" == ")" ]; then
            unset op_found
            paren=false
            ip="${ip[*]}"
            ip="${ip// /}"
            ip="${ip/\^/\*\*}"
            if [[ "${ip}" == *-* ]]; then
                # BASH isn't fully respecting PEMDAS,
                # so I split the input based on '-' as a delimiter.
                # I then calculate each side of the '-' before subtracting
                # the right side from the left site.
                IFS='-' read -ra ip_split <<< "${ip}"
                if [ "$2" == "--debug" ]; then
                    echo "ip_split = ${ip_split[*]}"
                fi
                first=$((ip_split[0]))
                if [ "$2" == "--debug" ]; then
                    echo -e "ip_split[0] = ${ip_split[0]}\nfirst = ${first}"
                fi
                second=$((ip_split[1]))
                if [ "$2" == "--debug" ]; then
                    echo -e "ip_split[1] = ${ip_split[1]}\nsecond = ${second}"
                fi
                ip=$((first - second))
                unset first second
            else
                ip=$(("${ip}"))
            fi
            if [ "$2" == "--debug" ]; then
                echo "ip = ${ip}"
            fi
            number_array+=("${ip}")
            unset ip
            continue
        # If the element is an operation, append to operation_array.
        elif [ "${final_array[${b}]}" == "^" ] \
            || [ "${final_array[${b}]}" == "*" ] \
            || [ "${final_array[${b}]}" == "/" ] \
            || [ "${final_array[${b}]}" == "%" ] \
            || [ "${final_array[${b}]}" == "+" ] \
            || [ "${final_array[${b}]}" == "-" ]; then
            if [ "${b}" == 0 ]; then
                if [ "${final_array[${b}]}" == "-" ]; then
                    # Do nothing, skipping the error. It's a negative number so it's all good.
                    negative_first=true
                else
                    echo "Currently other notations are unsupported, please start with a number." && exit 1
                fi
            fi
            
            if [ -n "${op_found}" ]; then
                echo "You may not have more than one operation between numbers." && exit 1
            fi
            
            op_found=true
            if [ "${paren}" == true ]; then
                ip+=("${final_array[${b}]}")
            else
                operation_array+=("${final_array[${b}]}")
                a=$((a + 1))
            fi
            if [ -n "${mn}" ]; then
                if [ "$2" == "--debug" ]; then
                    echo "mn = ${mn[*]}"
                fi
                numbers="${mn[*]}"
                unset mn
                numbers="${numbers// /}"
                if [ "$2" == "--debug" ]; then
                    echo "numbers = ${numbers}"
                fi
                number_array+=("${numbers}")
                unset numbers
                continue
            else
                continue
            fi
        # If the element is a number...
        elif [[ "${final_array[${b}]}" =~ ^[0-9]+$ ]]; then
            unset op_found
            # If "$paren" is set...
            if [ "${paren}" == true ]; then
                 ip+=("${final_array[${b}]}")
                 continue
            else
                mn+=("${final_array[${b}]}")
                continue
            fi
        fi
    fi
done

number_count="${#number_array[@]}"
operation_count="${#operation_array[@]}"

# Example for this snippet:
# `./bcalc 1+1+` would break the script.
# Adding this outputs an error message and gracefully exits.
if [ "${negative_first}" == true ] && [ "${operation_count}" -gt "${number_count}" ]; then
    echo "The count of operations shouldn't be greater than the count of numbers."
    exit 1
elif [ -z "${negative_first}" ] && [ "${operation_count}" -ge "${number_count}" ]; then
    echo "The count of operations shouldn't be greater than or equal to the count of numbers."
    exit 1
fi

if [ "$2" == "--debug" ]; then
    # This is useful for seeing the contents of each index of the number_array and operation_array variables.
    for ((d=0;d<=number_count;d++)); do
        echo "number_array[${d}] = ${number_array[${d}]}"
        for ((e=0;e<=operation_count;e++)); do
            echo "operation_array[${e}] = ${operation_array[${e}]}"
        done
    done
    # This is useful to make sure that the counts in the arrays are accurate to what they should be.
    echo -e "number_count = ${number_count}\noperation_count = ${operation_count}"
fi

## Finishing Touches
# Assemble the final calculation variable.
f=0
for ((g=0;g<=final_array_count;g++)); do
    if [ "${g}" = "${number_count}" ]; then
        break
    else
        if [ -n "${negative_first}" ]; then
            calculation_array+=("${operation_array[${f}]}")
            if [ "${g}" == "${operation_count}" ]; then
                continue
            else
                calculation_array+=("${number_array[${f}]}")
                f=$((f + 1))
                continue
            fi
        else
            calculation_array+=("${number_array[${f}]}")
            if [ "${g}" == "${operation_count}" ]; then
                continue
            else
                calculation_array+=("${operation_array[${f}]}")
                f=$((f + 1))
                continue
            fi
        fi
    fi
done

calculation="${calculation_array[*]}"
calculation="${calculation// /}"
calculation="${calculation/\^/\*\*}"
if [ "$2" == "--debug" ]; then
    echo "calculation = ${calculation}"
fi

## Output
# Output the result of the calculation without any formatting.
echo $((calculation))
